
package com.joysuch.wwyt.audit.service.impl;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.persistence.Id;
import javax.servlet.http.HttpServletRequest;

import com.joysuch.wwyt.audit.bean.AuditLogContext;
import com.joysuch.wwyt.audit.entity.AuditLogActionDef;
import com.joysuch.wwyt.audit.entity.AuditLogRecord;
import com.joysuch.wwyt.audit.service.AuditLogActionDefService;
import com.joysuch.wwyt.audit.service.AuditLogContextHolder;
import com.joysuch.wwyt.audit.service.AuditLogGenerator;
import com.joysuch.wwyt.audit.service.AuditLogProcessor;
import com.joysuch.wwyt.audit.service.EntityResolver;
import com.joysuch.wwyt.audit.service.ReturnResultParser;
import com.joysuch.wwyt.core.common.ajax.ResultBean;
import com.joysuch.wwyt.core.common.util.HttpUtils;
import com.joysuch.wwyt.core.service.BizCodeGeneratorFacade;
import com.joysuch.wwyt.core.support.Context;
import com.joysuch.wwyt.util.SpringBeanUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
 * 拦截dao层 save方法，针对添加操作，不是修改操作
 * 
 * @since
 *
 */
// @Aspect
public class AopAuditLogDaoGeneratorImpl implements AuditLogGenerator {

    private static final Logger logger = LoggerFactory.getLogger(AopAuditLogDaoGeneratorImpl.class);

    @Autowired
    private AuditLogActionDefService auditLogActionDefService;
    @Autowired
    private BizCodeGeneratorFacade bizCodeGeneratorFacade;
    @Autowired
    private EntityResolver entityResolver;
    @Autowired
    private AuditLogProcessor auditLogProcessor;

    private Map<Class, PropertyDescriptor> idFieldCache = new ConcurrentHashMap<>();

    @Pointcut("execution(public * com.joysuch.wwyt..repository..save(..))")
    public void generate2() {
    }

    public AuditLogRecord generate() {
        return null;
    }

    @Before("generate2()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {

    }

    @Around("generate2()")
    public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long s = System.currentTimeMillis();
        AuditLogActionDef def = null;
        AuditLogContext context = null;
        try {
            def = findActionDef(proceedingJoinPoint);
            if (def != null) {
                context = parseContextBeforeProcess(proceedingJoinPoint, def);
            }
            long s1 = System.currentTimeMillis();
            Object obj = proceedingJoinPoint.proceed();
            long s2 = System.currentTimeMillis();
            if (def != null) {
                if (!context.isDeleteOp()) {
                    // 非删除方法
                    updateContextInfo(context);
                }
                // 记录日志
                operationLogByReturnValue(def, context, obj);
            }
            long e = System.currentTimeMillis();
            logger.debug(String.format("拦截共花费%d+%d=%dms", s1 - s, e - s2, (e - s2 + s1 - s)));
            return obj;
        } catch (Exception e) {
            logger.error("审计日志异常：", e);
            if (def != null) {
                operationFailedByException(def, context, e);
            }
            throw e;
        }
    }

    private void updateContextInfo(AuditLogContext context) {
        if ("param".equals(context.getQueryEntityMethod())) {
            Object value = context.getParamValue();
            if (value != null) {
                if (value instanceof List) {
                    List list = entityResolver.getByProp(context.getEntityClass(), context.getNameField(),
                            (List) value);
                    context.setObject(list);
                } else if (value.getClass().isArray()) {
                    Object[] cache = new Object[Array.getLength(value)];
                    for (int i = 0; i < Array.getLength(value); i++) {
                        cache[i] = Array.get(value, i);
                    }
                    List list = entityResolver.getByProp(context.getEntityClass(), context.getNameField(), cache);
                    context.setObject(list);
                } else {
                    List list = entityResolver.getByProp(context.getEntityClass(), context.getNameField(), value);
                    context.setObject(list);
                }
            }
        } else if ("threadlocal".equals(context.getQueryEntityMethod())) {
            context.setObject(AuditLogContextHolder.get());
        } else {
        }
    }

    private AuditLogContext parseContextBeforeProcess(ProceedingJoinPoint proceedingJoinPoint, AuditLogActionDef def) {
        AuditLogContext context = new AuditLogContext();
        context.setEntityClass(loadClass(def.getOpObjectType()));
        context.setQueryEntityMethod(def.getQueryObjectMethod());
        if ("param".equals(def.getQueryObjectMethod())) {
            context.setNameField(def.getOpObjectNameAttr());
            context.setParamValue(getValueFromParams(proceedingJoinPoint, def.getQueryObjectParams()));
        } else if ("threadlocal".equals(def.getQueryObjectMethod())) {
            context.setNameField(def.getOpObjectNameAttr());
        } else {
            logger.warn("不识别的参数解析方法 {}", def.getQueryObjectMethod());
        }
        context.setDeleteOp("D".equalsIgnoreCase(def.getOpType()));
        return context;
    }

    private Object getValueFromParams(ProceedingJoinPoint proceedingJoinPoint, String queryObjectParams) {
        MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();

        Method method = methodSignature.getMethod();
        Parameter[] parameters = method.getParameters();
        String pname = queryObjectParams;
        if (queryObjectParams.contains(".")) {
            pname = queryObjectParams.substring(0, queryObjectParams.indexOf('.'));
        }
        for (int i = 0; i < parameters.length; i++) {
            Parameter p = parameters[i];
            if (p.getName().equals(pname)) {
                return SpringBeanUtils.extractChainValueFromArg(proceedingJoinPoint.getArgs()[i], queryObjectParams);
            }
        }
        return null;
    }

    private Class loadClass(String opObjectType) {
        if (opObjectType == null) {
            return null;
        }
        try {
            return Class.forName(opObjectType);
        } catch (Exception e) {
            logger.error("找不到类" + opObjectType, e);
        }
        return null;
    }

    private void operationLogByReturnValue(AuditLogActionDef def, AuditLogContext context, Object obj) {
        if (Context.getCurrentUserId() == null) {
            // 未登录状态,不予记录
            return;
        }
        try {
            AuditLogRecord r = new AuditLogRecord();
            r.setActionDefId(def.getId());
            r.setCode(String.valueOf(new Date().getTime()));
            r.setHappenTime(new Date());
            r.setIp(parseIp());
            r.setMac(null);
            r.setModule(def.getModule());
            r.setOperationName(def.getOperationName());
            // 判断执行状态
            r.setResult(judgeSuccessState(def, context, obj));
            r.setUserId(Context.getCurrentUserId());
            r.setUserName(Context.getCurrentUser().getRealName());
            r.setOpObjectType(def.getOpObjectType());
            r.setOpObjectValue(parseValueFromContext(context));
            r.setDescription(groupDescription(def.getOperationName(), getEntityName(context.getObject(), context)));

            auditLogProcessor.process(r);
        }catch (Throwable e) {
            logger.error("审计日志记录异常", e);
        }
    }

    private String groupDescription(String operationName, String opObjectValue) {
        if (opObjectValue != null) {
            return operationName + ":" + opObjectValue;
        }
        return operationName;
    }

    private Byte judgeSuccessState(AuditLogActionDef def, AuditLogContext context, Object obj) {
        if ("ResultBean".equals(def.getResultJudgeType())) {
            if (obj instanceof ResultBean) {
                return ((ResultBean) obj).getCode() == 0 ? (byte) 0 : (byte) -1;
            } else {
                // 不识别的参数值
                return 40;
            }
        } else if ("threadlocal".equals(def.getResultJudgeType())) {
            return AuditLogContextHolder.isSuccess() ? (byte) 0 : -1;
        } else if ("exception".equals(def.getResultJudgeType())) {
            return (byte) 0;
        } else {
            ReturnResultParser parser = parseReturnResultParser(def);
            if (parser != null) {
                return parser.parse(obj);
            }
        }
        // 布置如何解析
        return -2;
    }

    private ReturnResultParser parseReturnResultParser(AuditLogActionDef def) {
        try {
            if (def.getResultJudgeRule() != null) {
                return (ReturnResultParser) Class.forName(def.getResultJudgeRule()).newInstance();
            }
        } catch (Exception e) {
            logger.error("加载结果判断器错误:" + def.getResultJudgeRule(), e);
        }
        return null;
    }

    private void operationFailedByException(AuditLogActionDef def, AuditLogContext context, Exception e) {
        if (Context.getCurrentUserId() == null) {
            // 未登录状态,不予记录
            return;
        }
        AuditLogRecord r = new AuditLogRecord();
        r.setActionDefId(def.getId());
        r.setCode(String.valueOf(new Date().getTime()));
        r.setHappenTime(new Date());
        r.setIp(parseIp());
        r.setMac(null);
        r.setModule(def.getModule());
        r.setOperationName(def.getOperationName());
        r.setResult((byte) -1);
        r.setUserId(Context.getCurrentUserId());
        r.setUserName(Context.getCurrentUser().getRealName());
        r.setOpObjectType(def.getOpObjectType());
        r.setOpObjectValue(parseValueFromContext(context));
        r.setDescription("异常");// r.setDescription(e.getMessage());

        auditLogProcessor.process(r);
    }

    private String parseValueFromContext(AuditLogContext context) {
        if (context == null || context.getObject() == null) {
            return null;
        }

        Object v = context.getObject();
        if (v instanceof Collection) {
            StringBuilder sb = new StringBuilder();
            Collection list = (Collection) v;
            for (Object obj : list) {
                sb.append(getEntityId(obj, context)).append(",");
            }
            if (sb.length() > 0) {
                sb.deleteCharAt(sb.length() - 1);
            }
            return sb.toString();
        } else {
            Object id = getEntityId(v, context);
            return id == null ? null : String.valueOf(id);
        }
    }

    private Object getEntityId(Object v, AuditLogContext context) {
        PropertyDescriptor f = queryIdField(v);
        if (f == null) {
            return null;
        }
        Object value;
        try {
            value = f.getReadMethod().invoke(v);
            return value;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private PropertyDescriptor queryIdField(Object v) {
        return idFieldCache.computeIfAbsent(v.getClass(), e -> {
            return extractIdProp(e);
        });
    }

    private String getEntityName(Object v, AuditLogContext context) {
        PropertyDescriptor pd = BeanUtils.getPropertyDescriptor(v.getClass(), context.getNameField());
        if (pd != null) {
            pd.getReadMethod().setAccessible(false);
            Object ret;
            try {
                ret = pd.getReadMethod().invoke(pd, null);
                return ret == null ? null : String.valueOf(ret);
            } catch (Exception e) {
                logger.error("获取实体名称出错:" + v + "-" + context, e);
                e.printStackTrace();
            }
        }
        return null;
    }

    private String parseIp() {
        // 接收到请求，记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        return HttpUtils.getClientIp(request);
    }

    private AuditLogActionDef findActionDef(ProceedingJoinPoint proceedingJoinPoint) {
        MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
        System.out.println("hehe:" + methodSignature.toLongString());
        // Method method = methodSignature.getMethod();
        return findActionDefByUrl(new String[] { methodSignature.toLongString() });
    }

    private AuditLogActionDef findActionDefByUrl(String[] keys) {
        for (String k : keys) {
            AuditLogActionDef action = auditLogActionDefService.findOneByTriggerTypeAndValue("dao", k);
            System.err.println(k + ":" + action);
            if (action != null) {
                return action;
            }
        }
        return null;
    }

    private PropertyDescriptor extractIdProp(Class domainClass) {
        PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(domainClass);
        for (PropertyDescriptor pd : pds) {
            if (pd.getReadMethod().getAnnotation(Id.class) != null) {
                return pd;
            }
        }
        Class sc = domainClass.getSuperclass();
        if (domainClass.getSuperclass() != null && sc != Object.class) {
            return extractIdProp(sc);
        }
        return null;
    }
}
